package com.yqyd.common.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.yqyd.common.config.OssConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 阿里云OSS工具类
 * 支持MultipartFile和base64文件上传
 *
 * @author kouzi
 * @since 2024-01-01
 */
@Slf4j
@Component
public class OssUtil {

    private static final String CACHE_CONTROL = "max-age=31536000";
    private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
    private static final long MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");

    private static final Map<String, String> CONTENT_TYPE_MAP = new HashMap<>();

    static {
        CONTENT_TYPE_MAP.put("jpg", "image/jpeg");
        CONTENT_TYPE_MAP.put("jpeg", "image/jpeg");
        CONTENT_TYPE_MAP.put("png", "image/png");
        CONTENT_TYPE_MAP.put("gif", "image/gif");
        CONTENT_TYPE_MAP.put("bmp", "image/bmp");
        CONTENT_TYPE_MAP.put("webp", "image/webp");
        CONTENT_TYPE_MAP.put("pdf", "application/pdf");
        CONTENT_TYPE_MAP.put("doc", "application/msword");
        CONTENT_TYPE_MAP.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        CONTENT_TYPE_MAP.put("xls", "application/vnd.ms-excel");
        CONTENT_TYPE_MAP.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        CONTENT_TYPE_MAP.put("txt", "text/plain");
        CONTENT_TYPE_MAP.put("mp4", "video/mp4");
        CONTENT_TYPE_MAP.put("mp3", "audio/mpeg");
    }

    @Autowired
    private OssConfig ossConfig;

    /**
     * 上传MultipartFile文件到OSS
     *
     * @param file 要上传的文件
     * @return 文件访问URL
     * @throws RuntimeException 上传失败时抛出异常
     */
    public String uploadFile(MultipartFile file) {
        validateFile(file);
        log.info("开始上传文件: {}, 大小: {} bytes", file.getOriginalFilename(), file.getSize());

        try {
            String fileName = generateFileName(file.getOriginalFilename());
            ObjectMetadata metadata = createMetadata(file.getSize(), file.getContentType());

            return uploadToOss(file.getInputStream(), fileName, metadata, ossConfig.getBucket(), ossConfig.getUrl());
        } catch (IOException e) {
            throw new RuntimeException("文件读取失败: " + e.getMessage(), e);
        }
    }

    /**
     * 上传base64编码的文件到OSS
     *
     * @param base64Data base64编码的文件数据（包含data:image/jpeg;base64,前缀或纯base64字符串）
     * @param fileExtension 文件扩展名（如：jpg, png, pdf等）
     * @return 文件访问URL
     * @throws RuntimeException 上传失败时抛出异常
     */
    public String uploadBase64File(String base64Data, String fileExtension) {
        validateBase64Data(base64Data, fileExtension);
        log.info("开始上传base64文件, 扩展名: {}, 数据长度: {}", fileExtension, base64Data.length());

        try {
            String cleanBase64Data = cleanBase64Data(base64Data);
            byte[] fileBytes;

            try {
                fileBytes = Base64.getDecoder().decode(cleanBase64Data);
            } catch (IllegalArgumentException e) {
                throw new RuntimeException("base64解码失败，数据格式不正确: " + e.getMessage(), e);
            }

            if (fileBytes.length == 0) {
                throw new RuntimeException("解码后的文件数据为空");
            }

            validateFileSize(fileBytes.length);

            String fileName = generateFileNameWithExtension(fileExtension);
            ObjectMetadata metadata = createMetadata(fileBytes.length, getContentType(fileExtension));
            InputStream inputStream = new ByteArrayInputStream(fileBytes);

            return uploadToOss(inputStream, fileName, metadata, ossConfig.getBucket(), ossConfig.getUrl());
        } catch (IllegalArgumentException e) {
            throw new RuntimeException("base64数据处理错误: " + e.getMessage(), e);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("base64文件上传异常: " + e.getMessage(), e);
        }
    }

    /**
     * 上传大文件到大文件bucket
     *
     * @param file 要上传的大文件
     * @return 文件访问URL
     * @throws RuntimeException 上传失败时抛出异常
     */
    public String uploadBigFile(MultipartFile file) {
        validateFile(file);
        log.info("开始上传大文件: {}, 大小: {} bytes", file.getOriginalFilename(), file.getSize());

        try {
            String fileName = generateFileName(file.getOriginalFilename());
            ObjectMetadata metadata = createMetadata(file.getSize(), file.getContentType());
            String bigFileUrl = ossConfig.getUrl().replace(ossConfig.getBucket(), ossConfig.getBucketBig());

            return uploadToOss(file.getInputStream(), fileName, metadata, ossConfig.getBucketBig(), bigFileUrl);
        } catch (IOException e) {
            throw new RuntimeException("大文件读取失败: " + e.getMessage(), e);
        }
    }

    /**
     * 验证文件有效性
     */
    private void validateFile(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("上传文件不能为空");
        }
        validateFileSize(file.getSize());
    }

    /**
     * 验证base64数据
     */
    private void validateBase64Data(String base64Data, String fileExtension) {
        if (base64Data == null || base64Data.trim().isEmpty()) {
            throw new IllegalArgumentException("base64数据不能为空");
        }
        if (fileExtension == null || fileExtension.trim().isEmpty()) {
            throw new IllegalArgumentException("文件扩展名不能为空");
        }
        // 验证扩展名格式（只允许字母数字）
        String cleanExtension = fileExtension.trim().toLowerCase();
        if (!cleanExtension.matches("^[a-z0-9]+$")) {
            throw new IllegalArgumentException("文件扩展名格式不正确，只允许字母和数字: " + fileExtension);
        }
    }

    /**
     * 验证文件大小
     */
    private void validateFileSize(long fileSize) {
        if (fileSize > MAX_FILE_SIZE) {
            throw new IllegalArgumentException("文件大小超过限制: " + (MAX_FILE_SIZE / 1024 / 1024) + "MB");
        }
    }

    /**
     * 创建OSS客户端
     */
    private OSS createOssClient() {
        return new OSSClientBuilder().build(
                "https://" + ossConfig.getEndpoint(),
                ossConfig.getAccessKey(),
                ossConfig.getSecretKey()
        );
    }

    /**
     * 创建文件元数据
     */
    private ObjectMetadata createMetadata(long contentLength, String contentType) {
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentLength(contentLength);
        metadata.setContentType(contentType);
        metadata.setCacheControl(CACHE_CONTROL);
        return metadata;
    }

    /**
     * 核心上传方法
     */
    private String uploadToOss(InputStream inputStream, String fileName, ObjectMetadata metadata,
                               String bucket, String baseUrl) {
        if (inputStream == null) {
            throw new IllegalArgumentException("输入流不能为空");
        }
        if (fileName == null || fileName.trim().isEmpty()) {
            throw new IllegalArgumentException("文件名不能为空");
        }

        String objectKey = ossConfig.getFolder() + "/" + fileName;
        OSS ossClient = null;

        try {
            ossClient = createOssClient();
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, objectKey, inputStream, metadata);
            PutObjectResult result = ossClient.putObject(putObjectRequest);

            if (result == null || result.getETag() == null) {
                throw new RuntimeException("OSS上传失败：未返回有效结果");
            }

            String fileUrl = baseUrl + "/" + objectKey;
            log.info("文件上传成功: {}, URL: {}, ETag: {}", fileName, fileUrl, result.getETag());

            return fileUrl;
        } catch (com.aliyun.oss.OSSException e) {
            log.error("OSS服务异常: ErrorCode={}, ErrorMessage={}, RequestId={}",
                    e.getErrorCode(), e.getErrorMessage(), e.getRequestId());
            throw new RuntimeException("OSS服务异常: " + e.getErrorMessage(), e);
        } catch (com.aliyun.oss.ClientException e) {
            log.error("OSS客户端异常: {}", e.getMessage(), e);
            throw new RuntimeException("OSS客户端异常: " + e.getMessage(), e);
        } catch (Exception e) {
            log.error("OSS上传异常: {}", e.getMessage(), e);
            throw new RuntimeException("OSS上传异常: " + e.getMessage(), e);
        } finally {
            if (ossClient != null) {
                try {
                    ossClient.shutdown();
                } catch (Exception e) {
                    log.warn("关闭OSS客户端时发生异常: {}", e.getMessage());
                }
            }
        }
    }

    /**
     * 生成唯一文件名（从原始文件名提取扩展名）
     */
    private String generateFileName(String originalFilename) {
        String extension = null;
        if (originalFilename != null && !originalFilename.trim().isEmpty() && originalFilename.contains(".")) {
            int lastDotIndex = originalFilename.lastIndexOf(".");
            if (lastDotIndex < originalFilename.length() - 1) {
                extension = originalFilename.substring(lastDotIndex + 1).trim();
                // 确保扩展名不为空
                if (extension.isEmpty()) {
                    extension = null;
                }
            }
        }
        return generateFileNameWithExtension(extension);
    }

    /**
     * 生成唯一文件名（直接使用扩展名）
     */
    private String generateFileNameWithExtension(String extension) {
        String dateStr = LocalDateTime.now().format(DATE_FORMATTER);
        String uuid = UUID.randomUUID().toString().replace("-", "");

        String finalExtension = "";
        if (extension != null && !extension.trim().isEmpty()) {
            String cleanExtension = extension.trim().toLowerCase();
            // 验证扩展名只包含字母数字
            if (cleanExtension.matches("^[a-z0-9]+$")) {
                finalExtension = "." + cleanExtension;
            }
        }

        return dateStr + "/" + uuid + finalExtension;
    }

    /**
     * 清理base64数据，移除data:xxx;base64,前缀
     */
    private String cleanBase64Data(String base64Data) {
        if (base64Data == null) {
            throw new IllegalArgumentException("base64数据不能为null");
        }

        String cleanData = base64Data.trim();
        if (cleanData.contains(",")) {
            int commaIndex = cleanData.indexOf(",");
            if (commaIndex < cleanData.length() - 1) {
                cleanData = cleanData.substring(commaIndex + 1);
            } else {
                throw new IllegalArgumentException("base64数据格式错误：逗号后没有数据");
            }
        }

        // 移除所有空白字符
        cleanData = cleanData.replaceAll("\\s+", "");

        // 验证base64格式
        if (!cleanData.matches("^[A-Za-z0-9+/]*={0,2}$")) {
            throw new IllegalArgumentException("base64数据包含非法字符");
        }

        return cleanData;
    }

    /**
     * 根据文件扩展名获取Content-Type
     */
    private String getContentType(String extension) {
        if (extension == null) {
            return DEFAULT_CONTENT_TYPE;
        }
        return CONTENT_TYPE_MAP.getOrDefault(extension.toLowerCase(), DEFAULT_CONTENT_TYPE);
    }
}